因為沒有使用過 collection view,所以想趁此機會,來練習一下,原本以為 table view 熟練了之後,collection view 也用同樣的方式理解,但發現其實這兩種的本質上似乎不太一樣,collection view 與 collection view layout 有相當的關聯性,最後在實作的過程也遇到奇怪的問題,果然還是要實作,才會真正練習到解決問題的能力啊!(補充:經過比對,應該是 Xcode 11 的問題,但實際原因不明)
圖:集合視圖 (CollectionView) 顯示的樣式
將集合視圖添加到用戶界面時,應用的主要工作是管理與該集合視圖相關的數據。集合視圖從數據源物件獲取數據,該數據源對像是符合 UICollectionViewDataSource 協定的物件,由應用程式提供。集合視圖中的數據被組織成單獨的項目,然後可以被分組為多個部分以進行展示。項目是呈現的最小數據單位。例如,在照片應用中,一個項目可能是單個圖像。集合視圖使用單元在屏幕上顯示項目,該單元是數據源配置和提供的類 UICollectionViewCell 的實例。
除了其單元格外,集合視圖還可以使用其他類型的視圖來呈現數據。這些補充視圖可以是與各個單元格分離但仍傳達某種信息的部分頁眉和頁腳之類的東西。對補充視圖的支持是可選的,由集合視圖的佈局物件定義,該佈局物件還負責定義這些視圖的位置。 除了將其嵌入到用戶界面中之外,還使用 UICollectionView 物件的方法來確保項目的可視表示與數據源物件中的順序匹配。因此,無論何時在集合中添加,刪除或重新排列數據,都可以使用此類的方法來插入,刪除和重新排列相應的單元格。可以使用集合視圖物件來管理選定的項目,儘管對於這種行為,集合視圖使用其關聯的委任物件。
與集合視圖關聯的一個非常重要的物件是佈局物件,它是類 UICollectionViewLayout 的子類。佈局物件負責定義集合視圖中所有單元格和補充視圖的組織和位置。儘管佈局物件定義了它們的位置,但實際上並沒有將該訊息應用於相應的視圖。因為單元格和輔助視圖的創建涉及集合視圖與數據源物件之間的協調,所以集合視圖實際上將佈局訊息應用於視圖。因此,從某種意義上說,佈局物件就像另一個數據源,僅提供視覺訊息而非項目數據。
通常,在創建集合視圖時可以指定佈局物件,但也可以動態更改集合視圖的佈局。佈局物件儲存在屬性 collectionViewLayout 中。設置此屬性可立即直接更新佈局,而無需對更改進行動畫處理。如果要為更改設置動畫,則必須調用setCollectionViewLayout(_: animated: completion:)
方法。 如果要創建交互式過渡(由手勢識別器或觸摸事件驅動),請使用 startInteractiveTransition(to: completion:)
方法更改佈局物件。該方法將安裝一個中間佈局物件,其目的是與手勢識別器或事件處理代碼一起使用以追蹤過渡進度。當事件處理代碼確定轉換已完成時,它將調用finishInteractiveTransition()
或cancelInteractiveTransition()
方法以刪除中間佈局物件並安裝預期的目標佈局物件。
集合視圖的數據源物件既提供項目的內容,又提供用於顯示該內容的視圖。集合視圖首次加載其內容時,會要求其數據源為每個可見項提供一個視圖。為了簡化代碼的創建過程,集合視圖要求視圖出隊,而不是在代碼中創建視圖。有兩種使視圖出隊的方法。您使用的視圖取決於請求的視圖類型:
dequeueReusableCell(withReuseIdentifier: for:)
獲取集合視圖中某個項目的單元格。dequeueReusableSupplementaryView(ofKind: withReuseIdentifier: for:)
方法來獲取佈局物件請求的補充視圖。在調用這兩種方法中的任何一種之前,必須告訴集合視圖(如果不存在)如何創建相應的視圖。為此,必須在集合視圖中註冊一個類或一個 nib 文件。例如,在註冊單元格時,可以使用 register(_ cellClass: forCellWithReuseIdentifier:)
或 register(_ nib: forCellWithReuseIdentifier:)
方法。作為註冊過程的一部分,可以指定標識視圖用途的重用標識符。這與稍後使視圖出隊時使用的字符串相同。在委任方法中使適當的視圖出隊後,配置其內容並將其返回到集合視圖以供使用。從佈局物件獲取佈局信息後,集合視圖將其應用於視圖並顯示它。
與 table view 類似,可以使用兩種方法來建立 collection view,就不在此贅述。對於 collection view 並不是很熟悉,就使用直接在 view controller 加入的方式來當作範例,不過在實作過程中,踩到了一個奇怪的坑,還不明白是為什麼,後面會敘述如何解決這奇怪的坑。
先在 view controller 加入 collection view
Layout 會預設成 Flow 模式,並把 Items 改為 1,就類似 table view 的 cell
給定一個 Identifier 的名稱
在 cell 中加入一個 image view 並設定好與 cell 的 contraints,如下圖
加入一個新的檔案來設定 cell
把 UIImageView 的 outlets 拉進 collection cell 中
class ImageCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
}
View controller 程式碼部分,加入 UICollectionViewDelegate 和 UICollectionViewDataSource 的委任
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
// 省略
}
view controller 的程式
let picArray = ["1", "2", "3", "4", "5"]
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "data", for: indexPath) as! ImageCollectionViewCell
cell.imageView.image = UIImage(named: picArray[indexPath.row])
return cell
}
實際執行結果
奇怪的坑
原本不管怎麼設定好 cell 的大小,圖片始終會變成原本的大小,就設的 constraints 都像是沒有作用一樣,最後是點選 UIImageView,把 view 中的 Layout 由 Automatic 改成 Translates Mask Into Constraints 解決問題。